package obj

import (
	"showen/ast"
	"showen/num"
	"showen/tok"
	"testing"
)

func TestProcedureInspect(t *testing.T) {
	// Create a simple environment for testing
	env := NewEnvironment()

	tests := []struct {
		name     string
		proc     *Procedure
		expected string
	}{
		{
			name: "procedure with no parameters",
			proc: &Procedure{
				Name:   "test-func",
				Params: []string{},
				Body:   []ast.Exp{},
				Env:    env,
			},
			expected: "(procedure test-func)",
		},
		{
			name: "procedure with one parameter",
			proc: &Procedure{
				Name:   "add",
				Params: []string{"x"},
				Body:   []ast.Exp{},
				Env:    env,
			},
			expected: "(procedure add(x))",
		},
		{
			name: "procedure with multiple parameters",
			proc: &Procedure{
				Name:   "calculate",
				Params: []string{"x", "y", "z"},
				Body:   []ast.Exp{},
				Env:    env,
			},
			expected: "(procedure calculate(x y z))",
		},
		{
			name: "procedure with chinese name",
			proc: &Procedure{
				Name:   "加法",
				Params: []string{"甲", "乙"},
				Body:   []ast.Exp{},
				Env:    env,
			},
			expected: "(procedure 加法(甲 乙))",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.proc.Inspect()
			if result != tt.expected {
				t.Errorf("Procedure.Inspect() = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestProcedureType(t *testing.T) {
	env := NewEnvironment()
	proc := &Procedure{
		Name:   "test",
		Params: []string{},
		Body:   []ast.Exp{},
		Env:    env,
	}

	if proc.Type() != PROCEDURE_OBJ {
		t.Errorf("Procedure.Type() = %v, want %v", proc.Type(), PROCEDURE_OBJ)
	}
}

func TestProcedureWithBody(t *testing.T) {
	env := NewEnvironment()

	// Create some test expressions for the body
	identifierExp := &ast.IdentifierExp{
		Token: tok.Token{Type: tok.IDENTIFIER, Lexeme: "x"},
	}

	booleanExp := &ast.BooleanExp{
		Token: tok.Token{Type: tok.BOOLEAN, Lexeme: "#t"},
	}

	listExp := &ast.ListExp{
		Exps: []ast.Exp{
			&ast.IdentifierExp{Token: tok.Token{Type: tok.IDENTIFIER, Lexeme: "+"}},
			&ast.IdentifierExp{Token: tok.Token{Type: tok.IDENTIFIER, Lexeme: "x"}},
			&ast.IdentifierExp{Token: tok.Token{Type: tok.IDENTIFIER, Lexeme: "y"}},
		},
	}

	proc := &Procedure{
		Name:   "math-func",
		Params: []string{"x", "y"},
		Body:   []ast.Exp{identifierExp, booleanExp, listExp},
		Env:    env,
	}

	// The body is not included in the Inspect() output, so it should be the same as without body
	expected := "(procedure math-func(x y))"
	result := proc.Inspect()

	if result != expected {
		t.Errorf("Procedure.Inspect() = %v, want %v", result, expected)
	}
}

func TestIntegerObject(t *testing.T) {
	tests := []struct {
		name     string
		value    num.Number
		expected string
	}{
		{
			name:     "zero",
			value:    num.Integer(0),
			expected: "0",
		},
		{
			name:     "positive integer",
			value:    num.Integer(123),
			expected: "123",
		},
		{
			name:     "negative integer",
			value:    num.Integer(-456),
			expected: "-456",
		},
		{
			name:     "max int64",
			value:    num.Integer(int64(9223372036854775807)),
			expected: "9223372036854775807",
		},
		{
			name:     "min int64",
			value:    num.Integer(int64(-9223372036854775808)),
			expected: "-9223372036854775808",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			intObj := &NumberObject{Value: tt.value}
			if intObj.Type() != NUMBER_OBJ {
				t.Errorf("IntegerObject.Type() = %v, want %v", intObj.Type(), NUMBER_OBJ)
			}

			if intObj.Inspect() != tt.expected {
				t.Errorf("IntegerObject.Inspect() = %v, want %v", intObj.Inspect(), tt.expected)
			}
		})
	}
}

func TestPairObject(t *testing.T) {
	// Test creating a simple pair
	n1 := &NumberObject{Value: num.Integer(1)}
	n2 := &NumberObject{Value: num.Integer(2)}
	n3 := &NumberObject{Value: num.Integer(3)}

	pairObj := &PairObject{Car: n1, Cdr: n2}

	if pairObj.Type() != PAIR_OBJ {
		t.Errorf("Expected type PAIR_OBJ, got %v", pairObj.Type())
	}

	// Test dotted pair string representation
	expected := "(1 . 2)"
	if pairObj.Inspect() != expected {
		t.Errorf("Expected %s, got %s", expected, pairObj.Inspect())
	}

	// Test creating a list
	listObj := &PairObject{Car: n1, Cdr: &PairObject{Car: n2, Cdr: &PairObject{Car: n3, Cdr: EMPTY_LIST}}}

	expected = "(1 2 3)"
	if listObj.Inspect() != expected {
		t.Errorf("Expected %s, got %s", expected, listObj.Inspect())
	}
}

/*
	func TestListObject(t *testing.T) {
		// Test creating a list with elements
		elements := []Object{
			&NumberObject{Value: num.Integer(1)},
			&NumberObject{Value: num.Integer(2)},
			&NumberObject{Value: num.Integer(3)},
		}
		listObj := &ListObject{Elements: elements}

		if listObj.Type() != LIST_OBJ {
			t.Errorf("Expected type LIST_OBJ, got %v", listObj.Type())
		}

		expected := "(1 2 3)"
		if listObj.Inspect() != expected {
			t.Errorf("Expected %s, got %s", expected, listObj.Inspect())
		}
	}
*/
func TestCharacterObject(t *testing.T) {
	tests := []struct {
		name     string
		char     *CharacterObject
		expected string
	}{
		{
			name:     "regular character",
			char:     &CharacterObject{Value: 'a'},
			expected: "#\\a",
		},
		{
			name:     "space character",
			char:     &CharacterObject{Value: ' '},
			expected: "#\\space",
		},
		{
			name:     "newline character",
			char:     &CharacterObject{Value: '\n'},
			expected: "#\\newline",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.char.Type() != CHARACTER_OBJ {
				t.Errorf("CharacterObject.Type() = %v, want %v", tt.char.Type(), CHARACTER_OBJ)
			}

			if tt.char.Inspect() != tt.expected {
				t.Errorf("CharacterObject.Inspect() = %v, want %v", tt.char.Inspect(), tt.expected)
			}
		})
	}
}
